// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/controls/tree/tree_view.h"

#include <string>

#include "base/macros.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/base/models/tree_node_model.h"
#include "ui/views/controls/prefix_selector.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/test/views_test_base.h"

using ui::TreeModel;
using ui::TreeModelNode;
using ui::TreeNode;

using base::ASCIIToUTF16;

namespace views {

class TestNode : public TreeNode<TestNode> {
public:
    TestNode() { }
    ~TestNode() override { }

private:
    DISALLOW_COPY_AND_ASSIGN(TestNode);
};

// Creates the following structure:
// 'root'
//   'a'
//   'b'
//     'b1'
//   'c'
class TreeViewTest : public ViewsTestBase {
public:
    TreeViewTest()
        : model_(new TestNode)
    {
        static_cast<TestNode*>(model_.GetRoot())->SetTitle(ASCIIToUTF16("root"));
        Add(model_.GetRoot(), 0, "a");
        Add(Add(model_.GetRoot(), 1, "b"), 0, "b1");
        Add(model_.GetRoot(), 2, "c");
    }

protected:
    TestNode* Add(TestNode* parent,
        int index,
        const std::string& title);

    std::string TreeViewContentsAsString();

    std::string GetSelectedNodeTitle();

    std::string GetEditingNodeTitle();

    TestNode* GetNodeByTitle(const std::string& title);

    void IncrementSelection(bool next);
    void CollapseOrSelectParent();
    void ExpandOrSelectChild();
    int GetRowCount();
    PrefixSelector* selector() { return tree_.GetPrefixSelector(); }

    ui::TreeNodeModel<TestNode> model_;
    TreeView tree_;

private:
    std::string InternalNodeAsString(TreeView::InternalNode* node);

    TestNode* GetNodeByTitleImpl(TestNode* node, const base::string16& title);

    DISALLOW_COPY_AND_ASSIGN(TreeViewTest);
};

TestNode* TreeViewTest::Add(TestNode* parent,
    int index,
    const std::string& title)
{
    TestNode* new_node = new TestNode;
    new_node->SetTitle(ASCIIToUTF16(title));
    model_.Add(parent, new_node, index);
    return new_node;
}

std::string TreeViewTest::TreeViewContentsAsString()
{
    return InternalNodeAsString(&tree_.root_);
}

std::string TreeViewTest::GetSelectedNodeTitle()
{
    TreeModelNode* model_node = tree_.GetSelectedNode();
    return model_node ? base::UTF16ToASCII(model_node->GetTitle())
                      : std::string();
}

std::string TreeViewTest::GetEditingNodeTitle()
{
    TreeModelNode* model_node = tree_.GetEditingNode();
    return model_node ? base::UTF16ToASCII(model_node->GetTitle())
                      : std::string();
}

TestNode* TreeViewTest::GetNodeByTitle(const std::string& title)
{
    return GetNodeByTitleImpl(model_.GetRoot(), ASCIIToUTF16(title));
}

void TreeViewTest::IncrementSelection(bool next)
{
    tree_.IncrementSelection(next ? TreeView::INCREMENT_NEXT : TreeView::INCREMENT_PREVIOUS);
}

void TreeViewTest::CollapseOrSelectParent()
{
    tree_.CollapseOrSelectParent();
}

void TreeViewTest::ExpandOrSelectChild()
{
    tree_.ExpandOrSelectChild();
}

int TreeViewTest::GetRowCount()
{
    return tree_.GetRowCount();
}

TestNode* TreeViewTest::GetNodeByTitleImpl(TestNode* node,
    const base::string16& title)
{
    if (node->GetTitle() == title)
        return node;
    for (int i = 0; i < node->child_count(); ++i) {
        TestNode* child = GetNodeByTitleImpl(node->GetChild(i), title);
        if (child)
            return child;
    }
    return NULL;
}

std::string TreeViewTest::InternalNodeAsString(
    TreeView::InternalNode* node)
{
    std::string result = base::UTF16ToASCII(node->model_node()->GetTitle());
    if (node->is_expanded() && node->child_count()) {
        result += " [";
        for (int i = 0; i < node->child_count(); ++i) {
            if (i > 0)
                result += " ";
            result += InternalNodeAsString(node->GetChild(i));
        }
        result += "]";
    }
    return result;
}

// Verifies setting model correctly updates internal state.
TEST_F(TreeViewTest, SetModel)
{
    tree_.SetModel(&model_);
    EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
    EXPECT_EQ("root", GetSelectedNodeTitle());
    EXPECT_EQ(4, GetRowCount());
}

// Verifies SetSelectedNode works.
TEST_F(TreeViewTest, SetSelectedNode)
{
    tree_.SetModel(&model_);
    EXPECT_EQ("root", GetSelectedNodeTitle());

    // NULL should clear the selection.
    tree_.SetSelectedNode(NULL);
    EXPECT_EQ(std::string(), GetSelectedNodeTitle());

    // Select 'c'.
    tree_.SetSelectedNode(GetNodeByTitle("c"));
    EXPECT_EQ("c", GetSelectedNodeTitle());

    // Select 'b1', which should expand 'b'.
    tree_.SetSelectedNode(GetNodeByTitle("b1"));
    EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
    EXPECT_EQ("b1", GetSelectedNodeTitle());
}

// Makes sure SetRootShown doesn't blow up.
TEST_F(TreeViewTest, HideRoot)
{
    tree_.SetModel(&model_);
    tree_.SetRootShown(false);
    EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
    EXPECT_EQ("a", GetSelectedNodeTitle());
    EXPECT_EQ(3, GetRowCount());
}

// Expands a node and verifies the children are loaded correctly.
TEST_F(TreeViewTest, Expand)
{
    tree_.SetModel(&model_);
    tree_.Expand(GetNodeByTitle("b1"));
    EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
    EXPECT_EQ("root", GetSelectedNodeTitle());
    EXPECT_EQ(5, GetRowCount());
}

// Collapes a node and verifies state.
TEST_F(TreeViewTest, Collapse)
{
    tree_.SetModel(&model_);
    tree_.Expand(GetNodeByTitle("b1"));
    EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
    EXPECT_EQ(5, GetRowCount());
    tree_.SetSelectedNode(GetNodeByTitle("b1"));
    EXPECT_EQ("b1", GetSelectedNodeTitle());
    tree_.Collapse(GetNodeByTitle("b"));
    EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
    // Selected node should have moved to 'b'
    EXPECT_EQ("b", GetSelectedNodeTitle());
    EXPECT_EQ(4, GetRowCount());
}

// Verifies adding nodes works.
TEST_F(TreeViewTest, TreeNodesAdded)
{
    tree_.SetModel(&model_);
    EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
    // Add a node between b and c.
    Add(model_.GetRoot(), 2, "B");
    EXPECT_EQ("root [a b B c]", TreeViewContentsAsString());
    EXPECT_EQ("root", GetSelectedNodeTitle());
    EXPECT_EQ(5, GetRowCount());

    // Add a child of b1, which hasn't been loaded and shouldn't do anything.
    Add(GetNodeByTitle("b1"), 0, "b11");
    EXPECT_EQ("root [a b B c]", TreeViewContentsAsString());
    EXPECT_EQ("root", GetSelectedNodeTitle());
    EXPECT_EQ(5, GetRowCount());

    // Add a child of b, which isn't expanded yet, so it shouldn't effect
    // anything.
    Add(GetNodeByTitle("b"), 1, "b2");
    EXPECT_EQ("root [a b B c]", TreeViewContentsAsString());
    EXPECT_EQ("root", GetSelectedNodeTitle());
    EXPECT_EQ(5, GetRowCount());

    // Expand b and make sure b2 is there.
    tree_.Expand(GetNodeByTitle("b"));
    EXPECT_EQ("root [a b [b1 b2] B c]", TreeViewContentsAsString());
    EXPECT_EQ("root", GetSelectedNodeTitle());
    EXPECT_EQ(7, GetRowCount());
}

// Verifies removing nodes works.
TEST_F(TreeViewTest, TreeNodesRemoved)
{
    // Add c1 as a child of c and c11 as a child of c1.
    Add(Add(GetNodeByTitle("c"), 0, "c1"), 0, "c11");
    tree_.SetModel(&model_);

    // Remove c11, which shouldn't have any effect on the tree.
    EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
    EXPECT_EQ("root", GetSelectedNodeTitle());
    EXPECT_EQ(4, GetRowCount());

    // Expand b1, then collapse it and remove its only child, b1. This shouldn't
    // effect the tree.
    tree_.Expand(GetNodeByTitle("b"));
    tree_.Collapse(GetNodeByTitle("b"));
    delete model_.Remove(GetNodeByTitle("b1")->parent(), GetNodeByTitle("b1"));
    EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
    EXPECT_EQ("root", GetSelectedNodeTitle());
    EXPECT_EQ(4, GetRowCount());

    // Remove 'b'.
    delete model_.Remove(GetNodeByTitle("b")->parent(), GetNodeByTitle("b"));
    EXPECT_EQ("root [a c]", TreeViewContentsAsString());
    EXPECT_EQ("root", GetSelectedNodeTitle());
    EXPECT_EQ(3, GetRowCount());

    // Remove 'c11', shouldn't visually change anything.
    delete model_.Remove(GetNodeByTitle("c11")->parent(), GetNodeByTitle("c11"));
    EXPECT_EQ("root [a c]", TreeViewContentsAsString());
    EXPECT_EQ("root", GetSelectedNodeTitle());
    EXPECT_EQ(3, GetRowCount());

    // Select 'c1', remove 'c' and make sure selection changes.
    tree_.SetSelectedNode(GetNodeByTitle("c1"));
    EXPECT_EQ("c1", GetSelectedNodeTitle());
    delete model_.Remove(GetNodeByTitle("c")->parent(), GetNodeByTitle("c"));
    EXPECT_EQ("root [a]", TreeViewContentsAsString());
    EXPECT_EQ("root", GetSelectedNodeTitle());
    EXPECT_EQ(2, GetRowCount());

    tree_.SetRootShown(false);
    // Add 'b' select it and remove it. Because we're not showing the root
    // selection should change to 'a'.
    Add(GetNodeByTitle("root"), 1, "b");
    tree_.SetSelectedNode(GetNodeByTitle("b"));
    delete model_.Remove(GetNodeByTitle("b")->parent(), GetNodeByTitle("b"));
    EXPECT_EQ("root [a]", TreeViewContentsAsString());
    EXPECT_EQ("a", GetSelectedNodeTitle());
    EXPECT_EQ(1, GetRowCount());
}

// Verifies changing a node title works.
TEST_F(TreeViewTest, TreeNodeChanged)
{
    // Add c1 as a child of c and c11 as a child of c1.
    Add(Add(GetNodeByTitle("c"), 0, "c1"), 0, "c11");
    tree_.SetModel(&model_);

    // Change c11, shouldn't do anything.
    model_.SetTitle(GetNodeByTitle("c11"), ASCIIToUTF16("c11.new"));
    EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
    EXPECT_EQ("root", GetSelectedNodeTitle());
    EXPECT_EQ(4, GetRowCount());

    // Change 'b1', shouldn't do anything.
    model_.SetTitle(GetNodeByTitle("b1"), ASCIIToUTF16("b1.new"));
    EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
    EXPECT_EQ("root", GetSelectedNodeTitle());
    EXPECT_EQ(4, GetRowCount());

    // Change 'b'.
    model_.SetTitle(GetNodeByTitle("b"), ASCIIToUTF16("b.new"));
    EXPECT_EQ("root [a b.new c]", TreeViewContentsAsString());
    EXPECT_EQ("root", GetSelectedNodeTitle());
    EXPECT_EQ(4, GetRowCount());
}

// Verifies IncrementSelection() works.
TEST_F(TreeViewTest, IncrementSelection)
{
    tree_.SetModel(&model_);

    IncrementSelection(true);
    EXPECT_EQ("a", GetSelectedNodeTitle());
    IncrementSelection(true);
    EXPECT_EQ("b", GetSelectedNodeTitle());
    IncrementSelection(true);
    tree_.Expand(GetNodeByTitle("b"));
    IncrementSelection(false);
    EXPECT_EQ("b1", GetSelectedNodeTitle());
    IncrementSelection(true);
    EXPECT_EQ("c", GetSelectedNodeTitle());
    IncrementSelection(true);
    EXPECT_EQ("c", GetSelectedNodeTitle());

    tree_.SetRootShown(false);
    tree_.SetSelectedNode(GetNodeByTitle("a"));
    EXPECT_EQ("a", GetSelectedNodeTitle());
    IncrementSelection(false);
    EXPECT_EQ("a", GetSelectedNodeTitle());
}

// Verifies CollapseOrSelectParent works.
TEST_F(TreeViewTest, CollapseOrSelectParent)
{
    tree_.SetModel(&model_);

    tree_.SetSelectedNode(GetNodeByTitle("root"));
    CollapseOrSelectParent();
    EXPECT_EQ("root", TreeViewContentsAsString());
    EXPECT_EQ("root", GetSelectedNodeTitle());

    // Hide the root, which should implicitly expand the root.
    tree_.SetRootShown(false);
    EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
    EXPECT_EQ("a", GetSelectedNodeTitle());

    tree_.SetSelectedNode(GetNodeByTitle("b1"));
    EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
    EXPECT_EQ("b1", GetSelectedNodeTitle());
    CollapseOrSelectParent();
    EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
    EXPECT_EQ("b", GetSelectedNodeTitle());
    CollapseOrSelectParent();
    EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
    EXPECT_EQ("b", GetSelectedNodeTitle());
}

// Verifies ExpandOrSelectChild works.
TEST_F(TreeViewTest, ExpandOrSelectChild)
{
    tree_.SetModel(&model_);

    tree_.SetSelectedNode(GetNodeByTitle("root"));
    ExpandOrSelectChild();
    EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
    EXPECT_EQ("a", GetSelectedNodeTitle());

    ExpandOrSelectChild();
    EXPECT_EQ("root [a b c]", TreeViewContentsAsString());
    EXPECT_EQ("a", GetSelectedNodeTitle());

    tree_.SetSelectedNode(GetNodeByTitle("b"));
    ExpandOrSelectChild();
    EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
    EXPECT_EQ("b", GetSelectedNodeTitle());
    ExpandOrSelectChild();
    EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
    EXPECT_EQ("b1", GetSelectedNodeTitle());
    ExpandOrSelectChild();
    EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString());
    EXPECT_EQ("b1", GetSelectedNodeTitle());
}

// Verify selection is properly updated on each keystroke.
TEST_F(TreeViewTest, SelectOnKeyStroke)
{
    tree_.SetModel(&model_);
    tree_.ExpandAll(model_.GetRoot());
    selector()->InsertText(ASCIIToUTF16("b"));
    EXPECT_EQ("b", GetSelectedNodeTitle());
    selector()->InsertText(ASCIIToUTF16("1"));
    EXPECT_EQ("b1", GetSelectedNodeTitle());

    // Invoke OnViewBlur() to reset time.
    selector()->OnViewBlur();
    selector()->InsertText(ASCIIToUTF16("z"));
    EXPECT_EQ("b1", GetSelectedNodeTitle());

    selector()->OnViewBlur();
    selector()->InsertText(ASCIIToUTF16("a"));
    EXPECT_EQ("a", GetSelectedNodeTitle());
}

// Verifies edits are committed when focus is lost.
TEST_F(TreeViewTest, CommitOnFocusLost)
{
    tree_.SetModel(&model_);

    tree_.SetSelectedNode(GetNodeByTitle("root"));
    ExpandOrSelectChild();
    tree_.SetEditable(true);
    tree_.StartEditing(GetNodeByTitle("a"));
    tree_.editor()->SetText(ASCIIToUTF16("a changed"));
    tree_.OnDidChangeFocus(NULL, NULL);
    EXPECT_TRUE(GetNodeByTitle("a changed") != NULL);
}

} // namespace views
